﻿using System;
using System.Collections.Generic;
using System.Linq;
using Cysharp.Threading.Tasks;
using Internal.KuiHuaBaoDian.Services.UI;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

namespace KuiHuaBaoDian.Services.UGUI.Tips {

    public interface ITipsUI : IUI {

        ITipsPanel Panel { get; }
    }

    public interface ITipsPanel {

        GameObject GameObject { get; }

        void Show();
    }

    public interface ITipsService {

        void Register<T>(TipsGroupID groupID, ITipsGroup group) where T : ITipsUI;

        void Unregister(TipsGroupID groupID);

        void Add(Button button, TipsGroupID groupID, Func<bool> validate = null, Func<UniTask> beforeOpen = null, Action afterOpen = null);

        void Remove(Button button);

        void Close();
    }

    /// <summary>
    /// 提示服务
    /// </summary>
    public static class TipsService {

        /// <summary>
        /// 单例
        /// </summary>
        public static ITipsService Instance => m_Instance ??= KHBD.TypeMap.Instantiate<ITipsService>();
        private static ITipsService m_Instance;
    }

    [Implement(typeof(ITipsService))]
    internal sealed class KHBD_TipsService : ITipsService {

        private record Record(TipsGroupID ID, ITipsGroup Group, Type UIType, IDictionary<Button, ButtonRecord> ButtonRecordDictionary);

        private record ButtonRecord(CanvasGroup CanvasGroup, Func<bool> Validate, Func<UniTask> BeforeOpen, Action AfterOpen);

        private readonly IDictionary<TipsGroupID, Record> m_Dictionary = new Dictionary<TipsGroupID, Record>();
        private readonly IDictionary<Button, TipsGroupID> m_ButtonDictionary = new Dictionary<Button, TipsGroupID>();

        private Canvas m_BlockingCanvas;

        private UIHandle m_TipsUIHandle;

        public void Register<T>(TipsGroupID groupID, ITipsGroup group) where T : ITipsUI {
            var record = new Record(groupID, group, typeof(T), new Dictionary<Button, ButtonRecord>());
            m_Dictionary.TryAdd(groupID, record);
        }

        public void Add(Button button, TipsGroupID groupID, Func<bool> validate = null, Func<UniTask> beforeOpen = null, Action afterOpen = null) {
            if (!m_Dictionary.TryGetValue(groupID, out var record)) {
                throw new KeyNotFoundException($"没有添加过可用的 {nameof(groupID)} 相关Tips记录");
            }
            if (!button.TryGetComponent<CanvasGroup>(out var canvasGroup)) {
                canvasGroup = button.gameObject.AddComponent<CanvasGroup>();
            }
            if (!record.ButtonRecordDictionary.ContainsKey(button)) {
                record.ButtonRecordDictionary.Add(button, new ButtonRecord(canvasGroup, validate, beforeOpen, afterOpen));
            }
            m_ButtonDictionary[button] = groupID;
            button.onClick.AddListener(OnButtonClick);
        }

        private void OnButtonClick() => DoButtonClick().Forget();
        private async UniTaskVoid DoButtonClick() {
            var selected = EventSystem.current.currentSelectedGameObject;
            if (selected == null) {
                return;
            }
            var button = selected.GetComponent<Button>();
            button.interactable = false;
            var groupID = m_ButtonDictionary[button];
            var record = m_Dictionary[groupID];
            var buttonRecord = record.ButtonRecordDictionary[button];
            var validateResult = (buttonRecord.Validate == null) || buttonRecord.Validate.Invoke();
            if (!validateResult) {
                return;
            }
            ShowBlocking(button.GetComponentInParent<Canvas>());
            bool openSuccess = true;
            if (m_TipsUIHandle == null) {
                (UIService.Instance as IUIServiceInternal).SetAllBlocksRaycasts(false);
                openSuccess = false;
                m_TipsUIHandle = await UIService.Instance.Open(record.UIType, null, UIOpenOptions.Instantiate(false, false));
                if (m_TipsUIHandle != null) {
                    openSuccess = true;
                }
            }
            button.interactable = true;
            if (!openSuccess) {
                Close();
            }
            if (m_TipsUIHandle != null) {
                foreach (var kv in record.ButtonRecordDictionary) {
                    kv.Value.CanvasGroup.ignoreParentGroups = true;
                    kv.Value.CanvasGroup.blocksRaycasts = true;
                }
                var ui = (UIService.Instance as IUIServiceInternal).GetOpened(m_TipsUIHandle);
                var panel = (ui as ITipsUI).Panel;
                var gameObject = panel.GameObject;
                var rectTransform = gameObject.GetComponent<RectTransform>();
                var pointFive = new Vector2(.5f, .5f);
                rectTransform.anchorMin = pointFive;
                rectTransform.anchorMax = pointFive;
                var tipsUIBehaviourType = KHBD.TypeMap.Fetch<ITipsUIBehaviour>();
                if (!gameObject.TryGetComponent(tipsUIBehaviourType, out var tipsUIBehaviour)) {
                    tipsUIBehaviour = gameObject.AddComponent(tipsUIBehaviourType);
                }
                var buttonRect = button.transform as RectTransform;
                var worldPosition = buttonRect.TransformPoint(buttonRect.rect.center);
                (tipsUIBehaviour as ITipsUIBehaviour).Initalize(record.Group, worldPosition, buttonRect.rect);

                if (buttonRecord.BeforeOpen != null) {
                    await buttonRecord.BeforeOpen.Invoke();
                }
                panel.Show();
                UIService.Instance.SetActive(m_TipsUIHandle, true);
                //await UniTask.Yield();
                buttonRecord.AfterOpen?.Invoke();
                (tipsUIBehaviour as ITipsUIBehaviour).Apply();
            }
        }

        public void Remove(Button button) {
            button.onClick.RemoveListener(OnButtonClick);
            if (m_ButtonDictionary.TryGetValue(button, out var groupID) && button.TryGetComponent<CanvasGroup>(out var canvasGroup)) {
                var record = m_Dictionary[groupID];
                record.ButtonRecordDictionary.Remove(button);
            }
            m_ButtonDictionary.Remove(button);
        }

        public void Unregister(TipsGroupID groupID) {
            var removingSet = (from kv in m_ButtonDictionary where kv.Value == groupID select kv.Key).ToHashSet();
            foreach (var key in removingSet) {
                Remove(key);
            }
            m_Dictionary.Remove(groupID);
        }

        private void ShowBlocking(Canvas target) {
            if (m_BlockingCanvas == null) {
                m_BlockingCanvas = (UIService.Instance as IUIServiceInternal).InstantiateCanvas("tips_blocking");
                m_BlockingCanvas.transform.SetAsFirstSibling();
                m_BlockingCanvas.sortingLayerID = target.sortingLayerID;
                m_BlockingCanvas.sortingOrder = target.sortingOrder - 1;
                var gameObject = new GameObject("blocking");
                gameObject.transform.SetParent(m_BlockingCanvas.transform);
                gameObject.transform.localPosition = Vector3.zero;
                gameObject.transform.localScale = Vector3.one;
                gameObject.transform.localRotation = Quaternion.identity;
                var rectTransform = gameObject.AddComponent<RectTransform>();
                rectTransform.anchorMin = Vector2.zero;
                rectTransform.anchorMax = Vector2.one;
                rectTransform.offsetMin = Vector2.zero;
                rectTransform.offsetMax = Vector2.zero;
                var image = gameObject.AddComponent<Image>();
                image.color = new Color(0f, 0f, 0f, 0f);
                var button = gameObject.AddComponent<Button>();
                button.targetGraphic = image;
                button.transition = Selectable.Transition.None;
                var navigation = Navigation.defaultNavigation;
                navigation.mode = Navigation.Mode.None;
                button.navigation = navigation;
                button.onClick.AddListener(Close);
            } else {
                m_BlockingCanvas.gameObject.SetActive(true);
            }
        }

        public void Close() {
            if (m_BlockingCanvas != null) {
                (UIService.Instance as IUIServiceInternal).SetAllBlocksRaycasts(true);
                UIService.Instance.Close(m_TipsUIHandle);
                m_TipsUIHandle = null;
                m_BlockingCanvas.gameObject.SetActive(false);
            }
        }
    }
}
